﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;

namespace SimpleData.ObjectModel.Collections
{
	/// <summary>
	/// 
	/// </summary>
	public sealed class BitVector : IEnumerable<bool>
	{
		/// <summary>
		/// Quantidade de flags que um bucket pode armazenar
		/// </summary>
		private const int BucketSize = sizeof( uint ) * 8;
		private const uint LastBucketBit = 0x80000000; // Define onde está o último bit
		//private const uint LastBucketBit = 2147483648; // Define onde está o último bit

		private int capacity = 0;
		private uint[] vector;

		/// <summary>
		/// 
		/// </summary>
		/// <param name="capacity"></param>
		public BitVector( int capacity )
		{
			this.capacity = capacity;
			this.InitVector( capacity );
		}

		/// <summary>
		/// 
		/// </summary>
		public int Count
		{
			get { return this.capacity; }
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public bool this[ int index ]
		{
			get { return this.Get( index ); }
			set
			{
				this.vector[ this.GetBucketIndex( index ) ] |= this.GetBucketMask( index );
			}
		}

		/// <summary>
		/// Inicializa o vetor com as flags
		/// </summary>
		/// <param name="capacity"></param>
		private void InitVector( int capacity )
		{
			//int fragment = capacity % BucketSize;
			int len = capacity / BucketSize;
			//
			//this.vector = new uint[ len + fragment ];
			this.vector = new uint[ len + 1 ];
		}

		/// <summary>
		/// Realiza a inicializado dos vetor
		/// </summary>
		/// <param name="flags"></param>
		public void Init( uint flags )
		{
			this.Or( flags );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public bool Set( int index )
		{
			bool flag = this.Get( index );
			this.vector[ this.GetBucketIndex( index ) ] |= this.GetBucketMask( index );
			return flag;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public bool Reset( int index )
		{
			bool flag = this.Get( index );
			this.vector[ this.GetBucketIndex( index ) ] &= ~this.GetBucketMask( index );
			return flag;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public bool Get( int index )
		{
			return Convert.ToBoolean( this.vector[ this.GetBucketIndex( index ) ] & this.GetBucketMask( index ) );
		}

		/// <summary>
		/// Realiza a negação de todos os bits do vetor
		/// </summary>
		/// <returns></returns>
		public BitVector Not()
		{
			BitVector result = new BitVector( this.Count );

			for ( int index = 0; index < this.Count; index++ )
				result.vector[ index ] = ~this.vector[ index ];

			return result;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="flags"></param>
		/// <returns></returns>
		public BitVector Xor( uint flags )
		{
			return this.Xor( BitVector.Create( flags ) );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="anotherVector"></param>
		/// <returns></returns>
		public BitVector Xor( BitVector anotherVector )
		{
			return this.ApplyOperator( anotherVector, ( x, y ) => x ^ y );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="flags"></param>
		/// <returns></returns>
		public BitVector And( uint flags )
		{
			return this.And( BitVector.Create( flags ) );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="anotherVector"></param>
		/// <returns></returns>
		public BitVector And( BitVector anotherVector )
		{
			return this.ApplyOperator( anotherVector, ( x, y ) => x & y );
		}

		/// <summary>
		/// Retorna um novo vetor de bits aplicando a operação OR
		/// </summary>
		/// <param name="flags"></param>
		/// <returns></returns>
		public BitVector Or( uint flags )
		{
			return BitVector.Create( flags );
		}

		/// <summary>
		/// Retorna um novo vetor de bits aplicando a operação OR
		/// </summary>
		/// <param name="anotherVector"></param>
		/// <returns></returns>
		public BitVector Or( BitVector anotherVector )
		{
			return this.ApplyOperator( anotherVector, ( x, y ) => x | y );		
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="values"></param>
		//public void Write( int flags )
		//{
		//    byte[] raw = BitConverter.GetBytes( flags );
		//    int len = Math.Min( raw.Length, this.vector.
			
		//}

		/// <summary>
		/// Applica a operação <paramref name="op"/> em todos os buckets dos vetores
		/// </summary>
		/// <param name="anotherVector"></param>
		/// <param name="op"></param>
		/// <returns></returns>
		private BitVector ApplyOperator( BitVector anotherVector, Func<uint, uint, uint> op )
		{
			int minCount = Math.Min( this.Count, anotherVector.Count );
			BitVector result = new BitVector( Math.Max( this.Count, anotherVector.Count ) );

			// Realiza a operação com cada conjunto de flags
			for ( int index = 0; index < minCount; index++ )
				result.vector[ index ] = op( this.vector[ index ], anotherVector.vector[ index ] );

			//
			if ( this.Count > minCount )
				for ( int index = minCount; index < this.Count; index++ )
					result.vector[ index ] = op( this.vector[ index ], 0 ); //this.vector[ index ];
			else
				for ( int index = minCount; index < this.Count; index++ )
					result.vector[ index ] = op( 0, anotherVector.vector[ index ] ); //anotherVector.vector[ index ];

			return result;
		}

		/// <summary>
		/// Obtém a máscara para acessar a flag que está no <paramref name="index"/>
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		private uint GetBucketMask( int index )
		{
			int bit = index % BucketSize;
			uint mask = 1;
			mask <<= bit;

			return mask;
		}

		/// <summary>
		/// Obtém o índice do vetor <paramref name="vector"/> em que a flag está localizada
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		private int GetBucketIndex( int index )
		{
			return index / BucketSize;
		}

		/// <summary>
		/// Percorre todas as flags
		/// </summary>
		/// <returns></returns>
		public IEnumerator<bool> GetEnumerator()
		{
			int count = this.Count;
			int bucketIndex = 0;
			uint mask = 1;

			while ( count > 0 )
			{
				uint bucket = this.vector[ bucketIndex ];
				// Indica quando deve parar
				uint stopMark;
				if ( count < BucketSize )
					stopMark = ( ( uint )1 << count );
				else
					stopMark = LastBucketBit;

				while ( mask < stopMark )
				{
					yield return Convert.ToBoolean( bucket & mask );

					mask <<= 1;
					count--;
				}

				bucketIndex++;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return this.GetEnumerator();
		}


		/// <summary>
		/// Realiza a criação do vetor de bits e inicializa com 
		/// as flags <paramref name="flags"/>.
		/// </summary>
		/// <param name="flags"></param>
		/// <returns></returns>
		public static BitVector Create( uint flags )
		{
			BitVector array = new BitVector( BucketSize );
			array.vector[ 0 ] = flags;
			return array;
		}
	}
}
